﻿using System;
using System.Linq;
using AutoMapper;
using Enferno.Public.InversionOfControl;
using Enferno.Public.Web.Builders;
using Enferno.Public.Web.Mappers.ProductProfiles;
using Enferno.Public.Web.Mappers.Resolvers;
using Enferno.Public.Web.Models;
using Enferno.Public.Web.ViewModels;
using Enferno.StormApiClient.Products;
using Enferno.Web.StormUtils;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace Enferno.Public.Web.Test.Extending
{
    [TestClass]
    public class ExtendProductTests
    {
        [TestInitialize]
        public void Initialize()
        {
            var rules = MockRepository.GenerateMock<ISiteRules>();
            IoC.RegisterInstance(typeof(ISiteRules), rules);
            Mapper.AddProfile<ProductProfile>();
        }

        //**************************************************** Test 1 ****************************************************
        //Extend product with a property that is not already mapped

        //the new product model, here we want to include the EanCode property that is also present in the product entity
        class ExtendedProductModel : ProductModel
        {
            public string EanCode { get; set; }
        }

        //the product builder that can handle the new extended product model
        class ExtendedProductBuilder :
            ProductCustomProductBuilder<ExtendedProductModel>
        {
            public ExtendedProductBuilder(ISiteRules rules): base(rules) {}
        }

        //the map profile that understands how to map from Product (entity) -> ExtendedProductModel. We don't have to add any
        //special mapping here since automapper will map the EanCode property from Product automatically.
        class ExtendedProductMapProfile :
            ProductToCustomProductModelProfile<ExtendedProductModel>
        {
        }

        [TestMethod]
        public void CanBuildWithExtendedProductModel()
        {
            var rules = MockRepository.GenerateMock<ISiteRules>();

            //add the extended mapping needed to map the new property in the extended product model
            Mapper.Configuration.AddProfile<ExtendedProductMapProfile>();

            var product = new Product
            {
                Id = 1,
                EanCode = "some ean code value"
            };

            var builder = new ExtendedProductBuilder(rules);

            var stormContext = MockRepository.GenerateMock<IStormContext>();
            stormContext.Stub(x => x.Configuration).Return(new StormConfigurationSection());
            stormContext.Stub(x => x.ShowPricesIncVat).Return(false);
            stormContext.Stub(x => x.CultureCode).Return("sv");
            StormContext.SetInstance(stormContext);

            var model = builder.BuildProductModel(product);

            Assert.AreEqual(product.Id, model.Id);
            Assert.AreEqual(product.EanCode, model.EanCode);
        }

        //**************************************************** Test 2 ****************************************************
        //Modify the varaint model class.

        //the new variant model
        class ExtendedVariantModel : VariantModel
        {
            //the property have a different name then in the input object (EanCode)
            public string MyEanCode { get; set; }
        }

        //we need an overloaded product model as well since it will hold the custom variant model
        class ProductWithDifferentVariantModel :
            ProductWithCustomVariantModel<ExtendedVariantModel>
        {
        }

        //new builder that will work with the two new model types
        private class ExtendedProductBuilder2 :
            ProductCustomVariantBuilder
                <ProductWithDifferentVariantModel, ExtendedVariantModel>
        {
            public ExtendedProductBuilder2(ISiteRules rules): base(rules) {}
        }

        //build a mapper for Product (entity) -> ProductWithDifferentVaraiantModel
        class ProductWithDifferentVariantMapProfile :
            ProductToCustomProductWithCustomVariantModelProfile<ProductWithDifferentVariantModel, ExtendedVariantModel>
        {
        }

        //build a mapper for Product (entity) -> ExtendedVariantModel
        class ExtendedVairantMapProfile :
            ProductToCustomVariantModelProfile<ExtendedVariantModel>
        {
            public ExtendedVairantMapProfile()
            {
                //since we do a custom mapping here, from product.EanCode to varaint.MyEanCode we need to set the mapping
                //property with a map that contains the custom mapping.
                Mapping = Mapper.CreateMap<Product, ExtendedVariantModel>()
                    .ForMember(to => to.MyEanCode, opts => opts.MapFrom(from => from.EanCode));
            }
        }

        [TestMethod]
        public void CanBuildWithExtendedVariantModel()
        {
            var rules = MockRepository.GenerateMock<ISiteRules>();

            //add the extended mapping needed to map the new property in the extended product model
            Mapper.Configuration.AddProfile<ProductWithDifferentVariantMapProfile>();
            Mapper.Configuration.AddProfile<ExtendedVairantMapProfile>();

            var product = new Product
            {
                Id = 1,
                Variants = new ProductList
                {
                    new Product {Id = 1, EanCode = "ean1", PartNo = "2"},
                    new Product {Id = 1, EanCode = "ean2", PartNo = "3"}
                }
            };

            var builder = new ExtendedProductBuilder2(rules);

            var stormContext = MockRepository.GenerateMock<IStormContext>();
            stormContext.Stub(x => x.Configuration).Return(new StormConfigurationSection());
            stormContext.Stub(x => x.ShowPricesIncVat).Return(false);
            stormContext.Stub(x => x.CultureCode).Return("sv");
            StormContext.SetInstance(stormContext);

            var model = builder.BuildProductModel(product);

            Assert.AreEqual(product.Id, model.Id);
            Assert.AreEqual(2, model.Variants.Count);
            Assert.AreEqual("ean1", model.Variants.Single(variant => variant.PartNo == "2").MyEanCode);
            Assert.AreEqual("ean2", model.Variants.Single(variant => variant.PartNo == "3").MyEanCode);
        }

        //**************************************************** Test 3 ****************************************************
        //Extend product with a property that is differently named for a property that is already included in the base class.

        class ProductModelWithDifferentlyNamedPropertyAlreadyMappedInBaseClass : ProductModel
        {
            //the property have a different name then in the input object (EanCode)
            public OnHandStatusModel MyOnHandStatus { get; set; }
        }

        private class ExtendedProductBuilder3 : ProductCustomProductBuilder<ProductModelWithDifferentlyNamedPropertyAlreadyMappedInBaseClass>
        {
            public ExtendedProductBuilder3(ISiteRules rules) : base(rules)
            {
            }
        }

        private class ProductModelWithDifferentlyNamedMapProfile :
            ProductToCustomProductModelProfile<ProductModelWithDifferentlyNamedPropertyAlreadyMappedInBaseClass>
        {
            public ProductModelWithDifferentlyNamedMapProfile()
            {
                //since we do a custom mapping here, from product.EanCode to varaint.MyEanCode we need to set the mapping
                //property with a map that contains the custom mapping.
                Mapping = Mapper.CreateMap<Product, ProductModelWithDifferentlyNamedPropertyAlreadyMappedInBaseClass>()
                    .ForMember(to => to.MyOnHandStatus, opts => opts.ResolveUsing<ProductOnHandStatusResolver<OnHandStatusModel>>());
            }
        }

        [TestMethod]
        public void CanBuildWithProductModelWithDifferentlyNamedPropertyAlreadyMappedInBaseClass()
        {
            //add the extended mapping needed to map the new property in the extended product model
            Mapper.Configuration.AddProfile<ProductModelWithDifferentlyNamedMapProfile>();

            var product = new Product
            {
                Id = 1,
                OnHand = new OnHand()
                {
                    Value = 10
                }
            };

            var rules = MockRepository.GenerateMock<ISiteRules>();
            rules.Stub(x => x.GetOnHandStatus<OnHandStatusModel>(Arg<Product>.Is.Anything))
                .Return(new OnHandStatusModel() {Count = product.OnHand.Value});
            IoC.RegisterInstance(typeof (ISiteRules), rules);

            var stormContext = MockRepository.GenerateMock<IStormContext>();
            stormContext.Stub(x => x.Configuration).Return(new StormConfigurationSection());
            stormContext.Stub(x => x.ShowPricesIncVat).Return(false);
            stormContext.Stub(x => x.CultureCode).Return("sv");
            StormContext.SetInstance(stormContext);

            var builder = new ExtendedProductBuilder3(rules);

            var model = builder.BuildProductModel(product);

            Assert.AreEqual(product.Id, model.Id);
            Assert.AreEqual(product.OnHand.Value, model.MyOnHandStatus.Count);
        }

        //**************************************************** Test 4 ****************************************************
        // Extend a product and map it all the way to product view model

        class ExtendedProductModel2 : ProductBaseModel<VariantModel, ProductManufacturerModel, ProductListModel, ProductItemModel, FilterModel, ProductFileModel, PriceModel, OnHandStatusModel, ParametricModel, ParametricValueModel>
        {
            public string EanCode { get; set; }
        }

        class ExtendedProductViewModel : ProductViewModel
        {
            public string EanCode { get; set; }
        }

        class ExtendedProductBuilder4 : ProductBaseBuilder<ExtendedProductModel2, VariantModel, ProductManufacturerModel, ProductListModel, ProductItemModel, FilterModel, ProductFileModel, PriceModel, OnHandStatusModel, ParametricModel, ParametricValueModel>
        {
            public ExtendedProductBuilder4(ISiteRules rules) : base(rules) { }
        }

        private class ExtendedProductMapProfile2 :
            ProductToProductBaseModelProfile<ExtendedProductModel2, VariantModel, OnHandStatusModel, PriceModel, ProductFileModel, ProductManufacturerModel, ProductListModel, ProductItemModel, FilterModel, ParametricModel, ParametricValueModel>
        {
        }

        class ExtendedProductModelToExtendedProductViewModelMapProfile :
            CustomerProductModelToCustomProductViewModelProfile<ExtendedProductModel2, ExtendedProductViewModel>
        {
            
        } 
            
        [TestMethod]
        public void CanGetExtendedViewModelFromExtendedProductModel()
        {
            var rules = MockRepository.GenerateMock<ISiteRules>();

            //add the extended mapping needed to map the new property in the extended product model
            Mapper.Configuration.AddProfile<ExtendedProductMapProfile2>();
            Mapper.Configuration.AddProfile<ExtendedProductModelToExtendedProductViewModelMapProfile>();

            var product = new Product
            {
                Id = 1,
                EanCode = "some ean code value"
            };

            var builder = new ExtendedProductBuilder4(rules);

            var stormContext = MockRepository.GenerateMock<IStormContext>();
            stormContext.Stub(x => x.Configuration).Return(new StormConfigurationSection());
            stormContext.Stub(x => x.ShowPricesIncVat).Return(false);
            stormContext.Stub(x => x.CultureCode).Return("sv");
            StormContext.SetInstance(stormContext);

            var model = builder.BuildProductModel(product);

            var viewModel = Mapper.Map<ExtendedProductModel2, ExtendedProductViewModel>(model);

            Assert.AreEqual(product.Id, viewModel.Id);
            Assert.AreEqual(product.EanCode, viewModel.EanCode);
        }
    }
}
